/*  PrimeBlaster
    by Kjetil Eide 
    kjetile445@gmail.com

    This file is part of PrimeBlaster

    PrimeBlaster is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    PrimeBlaster is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PrimeBlaster.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.eide.spaceship;

import org.newdawn.slick.geom.Line;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.Input;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.Sound;

public class Laser {

	/* offset the start of the beam relative to center of space ship */

	private static final float OFFSET_X = 45;
	private static final float OFFSET_Y = -3;
	private static final float LASER_STRENGTH = 200;

	/* the space ship that fires the laser */
	private SpaceShip ship;
	
	/* the laser ray as a horizontal line. this will be
	 * translated and rotated into rayOriented
	 */
	private Line ray;

	/* line translated and rotated */
	private Line rayOriented;

	private int maxLength;

	private boolean on;
	private Image beamImage;
	private Sound beamLoop;
	private Sound beamOff;
	
	public Laser(int maxLength, SpaceShip ship) throws SlickException {
		ray = new Line(OFFSET_X, OFFSET_Y, maxLength, OFFSET_Y);
		this.ship = ship;
		on = false;
		beamImage = new Image("spacedata/beam.png");
		this.maxLength = maxLength;
		beamLoop = new Sound("spacedata/beam_loop2.ogg");
		beamOff = new Sound("spacedata/beam_off.ogg");
	}

	public void control(GameContainer c) {
		if (c.getInput().isKeyDown(Input.KEY_SPACE)) {
			if (!on) {
				on = true;
				beamLoop.loop();
			}
		} else if (on) {
			on = false;
			ray = new Line(OFFSET_X, OFFSET_Y, OFFSET_X + 1, OFFSET_Y);
			beamLoop.stop();
			beamOff.play();
		}
	}

	/**
	 * place the beam where the space ship is, and orient the
	 * beam according to orientation of the ship
	 * @param ship
	 */
	public void update(RigidBody ship) {
		Transform rotation = Transform.createRotateTransform(ship.getOrientation('R'));
		Transform translation = Transform.createTranslateTransform(ship.getX(),
				ship.getY());
		rayOriented = (Line) ray.transform(rotation);
		rayOriented = (Line) rayOriented.transform(translation);
	}

	/**
	 * Check if the laser hits anything. if so, add a force to the object hit
	 * and clip the laser beam so it doesn't go through the object.
	 * 
	 * @param rBody
	 */
	public Asteroid checkHit(int shipIdx, RBCollection rBody, float delta) {
		Vector2f minRay = new Vector2f(Integer.MAX_VALUE, Integer.MAX_VALUE);
		Vector2f closestIntersectPoint = null;
		boolean hit = false;
		int asteroidHitIdx = 0; /* index of the asteroid that was hit */
		
		for (int i = 0; i < rBody.getNumberOfRB(); i++) {
			if (rBody.getRB(i) instanceof Asteroid) {
				Shape rbOutline = rBody.getRB(i).getShape();
				int pointCount = rbOutline.getPointCount();

				Vector2f rayToEdge;

				for (int j = 0; j < pointCount; j++) {
					float x1 = rbOutline.getPoint(j)[0];
					float y1 = rbOutline.getPoint(j)[1];
					float x2 = rbOutline.getPoint((j + 1) % (pointCount))[0];
					float y2 = rbOutline.getPoint((j + 1) % (pointCount))[1];

					Line edge = new Line(x1, y1, x2, y2);
					
					Vector2f intersectPoint = rayOriented.intersect(edge, true);
					
					if (intersectPoint != null) {
						rayToEdge = 
							new Vector2f(intersectPoint).sub(rBody.getRB(shipIdx).getPos());
						if (rayToEdge.lengthSquared() < minRay.lengthSquared()) {
							minRay = rayToEdge;
							closestIntersectPoint = intersectPoint;
							asteroidHitIdx = i;
							hit = true;
						}
					}
				}
			}
		}

		Asteroid asteroidHit = null;
		Vector2f rayStart = new Vector2f(OFFSET_X, OFFSET_Y);
		float rayLength;
		
		if (hit) {
			/* the ray hit something, clip it 
			 * and add a force to the asteroid */
			
			asteroidHit = (Asteroid) rBody.getRB(asteroidHitIdx);
			rayLength = minRay.length();
			Vector2f laserForce = minRay.normalise().scale(LASER_STRENGTH);
			Vector2f cmToHitPoint = new Vector2f(closestIntersectPoint);
			cmToHitPoint.sub(asteroidHit.getPos());
			asteroidHit.addForce(laserForce, cmToHitPoint);
			asteroidHit.reduceStrength(delta);
			
		} else {
			/* the ray didn't hit anything, increase the length of it */
			
			rayLength = ray.getX2() + delta; 
			if (rayLength > maxLength) { 
				rayLength = maxLength;
			}			
		}

		ray.set(rayStart, new Vector2f(rayLength, OFFSET_Y));
		
		return asteroidHit;
	}
	
	public void turnOff() {
		on = false;
	}

	public float getOrientation(char type) {
		return ship.getOrientation(type);
	}
	
	public Image getImage() {
		return beamImage;
	}

	public Line getRayOriented() {
		return rayOriented;
	}

	public boolean isOn() {
		return on;
	}
	
	public void reset() {
		beamLoop.stop();
		on = false;
	}
	
}
